{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:41:29.893914Z",
     "start_time": "2025-01-20T07:41:27.487296Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:58:24.362721Z",
     "iopub.status.busy": "2025-01-21T11:58:24.362369Z",
     "iopub.status.idle": "2025-01-21T11:58:26.466764Z",
     "shell.execute_reply": "2025-01-21T11:58:26.466213Z",
     "shell.execute_reply.started": "2025-01-21T11:58:24.362696Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "```shell\n",
    "$ tree -L 1 cifar-10                                    \n",
    "cifar-10\n",
    "├── sampleSubmission.csv\n",
    "├── test\n",
    "├── train\n",
    "└── trainLabels.csv\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:58:30.771076Z",
     "iopub.status.busy": "2025-01-21T11:58:30.770623Z",
     "iopub.status.idle": "2025-01-21T11:58:31.618010Z",
     "shell.execute_reply": "2025-01-21T11:58:31.617400Z",
     "shell.execute_reply.started": "2025-01-21T11:58:30.771050Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "kaggle                            1.6.17\n",
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip list|grep kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:58:33.252949Z",
     "iopub.status.busy": "2025-01-21T11:58:33.252574Z",
     "iopub.status.idle": "2025-01-21T11:58:33.927718Z",
     "shell.execute_reply": "2025-01-21T11:58:33.927123Z",
     "shell.execute_reply.started": "2025-01-21T11:58:33.252925Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "- path is now set to: /content\n"
     ]
    }
   ],
   "source": [
    "!mkdir -p ~/.kaggle\n",
    "!cp /mnt/workspace/kaggle.json ~/.kaggle/\n",
    "!chmod 600 ~/.kaggle/kaggle.json\n",
    "!kaggle config set -n path -v /content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:58:41.116311Z",
     "iopub.status.busy": "2025-01-21T11:58:41.115950Z",
     "iopub.status.idle": "2025-01-21T12:00:15.491516Z",
     "shell.execute_reply": "2025-01-21T12:00:15.490921Z",
     "shell.execute_reply.started": "2025-01-21T11:58:41.116287Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading cifar-10.zip to /content/competitions/cifar-10\n",
      "100%|████████████████████████████████████████| 715M/715M [01:29<00:00, 7.56MB/s]\n",
      "100%|████████████████████████████████████████| 715M/715M [01:29<00:00, 8.38MB/s]\n"
     ]
    }
   ],
   "source": [
    "!kaggle competitions download -c cifar-10 #下载比赛数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:00:20.739187Z",
     "iopub.status.busy": "2025-01-21T12:00:20.738815Z",
     "iopub.status.idle": "2025-01-21T12:00:20.855017Z",
     "shell.execute_reply": "2025-01-21T12:00:20.854454Z",
     "shell.execute_reply.started": "2025-01-21T12:00:20.739163Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cifar-10.zip\n"
     ]
    }
   ],
   "source": [
    "!ls /content/competitions/cifar-10/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:00:32.724026Z",
     "iopub.status.busy": "2025-01-21T12:00:32.723661Z",
     "iopub.status.idle": "2025-01-21T12:00:36.502576Z",
     "shell.execute_reply": "2025-01-21T12:00:36.501989Z",
     "shell.execute_reply.started": "2025-01-21T12:00:32.724002Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Archive:  /content/competitions/cifar-10/cifar-10.zip\n",
      "  inflating: sampleSubmission.csv    \n",
      "  inflating: test.7z                 \n",
      "  inflating: train.7z                \n",
      "  inflating: trainLabels.csv         \n"
     ]
    }
   ],
   "source": [
    "!unzip /content/competitions/cifar-10/cifar-10.zip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:10.590417Z",
     "iopub.status.busy": "2025-01-21T12:01:10.590061Z",
     "iopub.status.idle": "2025-01-21T12:01:10.705889Z",
     "shell.execute_reply": "2025-01-21T12:01:10.705328Z",
     "shell.execute_reply.started": "2025-01-21T12:01:10.590392Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "04_10_monkeys_model_1_aliyun.ipynb\t\t       runs\n",
      "05_10_monkeys_model_2_resnet50_finetune_2aliyun.ipynb  sampleSubmission.csv\n",
      "06_cifar10_model_2-aliyun.ipynb\t\t\t       test.7z\n",
      "checkpoints\t\t\t\t\t       train.7z\n",
      "kaggle.json\t\t\t\t\t       trainLabels.csv\n"
     ]
    }
   ],
   "source": [
    "!ls"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "解压训练集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:20.737758Z",
     "iopub.status.busy": "2025-01-21T12:01:20.737370Z",
     "iopub.status.idle": "2025-01-21T12:01:44.338632Z",
     "shell.execute_reply": "2025-01-21T12:01:44.338052Z",
     "shell.execute_reply.started": "2025-01-21T12:01:20.737732Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.cloud.aliyuncs.com/pypi/simple\n",
      "Collecting py7zr\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/d0/59/dd1750002c0f46099281116f8165247bc62dc85edad41cdd26e7b26de19d/py7zr-0.22.0-py3-none-any.whl (67 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m67.9/67.9 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting texttable (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl (10 kB)\n",
      "Collecting pycryptodomex>=3.16.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/46/3f/f5bef92b11750af9e3516d4e69736eeeff20a2818d34611508bef5a7b381/pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.3 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.3/2.3 MB\u001b[0m \u001b[31m43.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hCollecting pyzstd>=0.15.9 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/6b/3e/e4c7f449af9d19975ff5d333a58330317cf8b05fe4754106c694a29e7c25/pyzstd-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (413 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m413.7/413.7 kB\u001b[0m \u001b[31m6.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hCollecting pyppmd<1.2.0,>=1.1.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/8f/f7/be56704aef53490da6bbe6e2e7efff3c13383310ac71f8a82d15d84bbedb/pyppmd-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (138 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.0/139.0 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting pybcj<1.1.0,>=1.0.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/d0/6a/3937d0915590d91ccab40b687a1c87429b17325240e8db7e9ad828c9cae9/pybcj-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (49 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.6/49.6 kB\u001b[0m \u001b[31m3.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting multivolumefile>=0.2.3 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/22/31/ec5f46fd4c83185b806aa9c736e228cb780f13990a9cf4da0beb70025fcc/multivolumefile-0.2.3-py3-none-any.whl (17 kB)\n",
      "Collecting inflate64<1.1.0,>=1.0.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/b3/3f/1af3164e8bda3fdce692c260f01e37c0af21c3022e7227795524505d8b21/inflate64-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (93 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m93.3/93.3 kB\u001b[0m \u001b[31m5.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting brotli>=1.1.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (3.0 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.0/3.0 MB\u001b[0m \u001b[31m67.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: psutil in /usr/local/lib/python3.10/site-packages (from py7zr) (6.1.1)\n",
      "Installing collected packages: texttable, brotli, pyzstd, pyppmd, pycryptodomex, pybcj, multivolumefile, inflate64, py7zr\n",
      "Successfully installed brotli-1.1.0 inflate64-1.0.1 multivolumefile-0.2.3 py7zr-0.22.0 pybcj-1.0.3 pycryptodomex-3.21.0 pyppmd-1.1.1 pyzstd-0.16.2 texttable-1.7.0\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install py7zr\n",
    "import py7zr\n",
    "a =py7zr.SevenZipFile(r'./train.7z','r')\n",
    "a.extractall(path=r'./competitions/cifar-10/')\n",
    "a.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "解压测试集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T06:17:43.607877Z",
     "iopub.status.busy": "2025-01-21T06:17:43.607512Z",
     "iopub.status.idle": "2025-01-21T06:19:24.394799Z",
     "shell.execute_reply": "2025-01-21T06:19:24.394257Z",
     "shell.execute_reply.started": "2025-01-21T06:17:43.607850Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "a =py7zr.SevenZipFile(r'./test.7z','r')\n",
    "a.extractall(path=r'./competitions/cifar-10/')\n",
    "a.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:07:56.749066Z",
     "iopub.status.busy": "2025-01-21T12:07:56.748574Z",
     "iopub.status.idle": "2025-01-21T12:07:56.929254Z",
     "shell.execute_reply": "2025-01-21T12:07:56.928622Z",
     "shell.execute_reply.started": "2025-01-21T12:07:56.749029Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "50000\n"
     ]
    }
   ],
   "source": [
    "!ls competitions/cifar-10/train/ |wc -l  #wc -l统计数量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:08:06.528294Z",
     "iopub.status.busy": "2025-01-21T12:08:06.527938Z",
     "iopub.status.idle": "2025-01-21T12:08:07.165983Z",
     "shell.execute_reply": "2025-01-21T12:08:07.165199Z",
     "shell.execute_reply.started": "2025-01-21T12:08:06.528270Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "300000\n"
     ]
    }
   ],
   "source": [
    "!ls competitions/cifar-10/test/ |wc -l "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:51:56.103713Z",
     "start_time": "2025-01-20T07:51:54.990355Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:09:30.115539Z",
     "iopub.status.busy": "2025-01-21T12:09:30.115164Z",
     "iopub.status.idle": "2025-01-21T12:09:32.280552Z",
     "shell.execute_reply": "2025-01-21T12:09:32.280012Z",
     "shell.execute_reply.started": "2025-01-21T12:09:30.115515Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\".\")\n",
    "DATA_DIR1 =Path(\"competitions/cifar-10/\")\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\" #测试集模板csv文件\n",
    "train_folder = DATA_DIR1 / \"train\"\n",
    "test_folder = DATA_DIR1 / \"test\"\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "def parse_csv_file(filepath, folder): #filepath:csv文件路径，folder:图片所在文件夹\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    #读取所有行\n",
    "    with open(filepath, 'r') as f:\n",
    "#         lines = f.readlines()  为什么加[1:]，可以试这个\n",
    "        #第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:] \n",
    "    for line in lines:#依次去取每一行\n",
    "        image_id, label_str = line.strip('\\n').split(',') #图片id 和标签分离\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        results.append((image_full_path, label_str)) #得到对应图片的路径和分类\n",
    "    return results\n",
    "\n",
    "#解析对应的文件夹\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "#打印\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:52:54.792839Z",
     "start_time": "2025-01-20T07:52:54.757327Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:09:46.277173Z",
     "iopub.status.busy": "2025-01-21T12:09:46.276848Z",
     "iopub.status.idle": "2025-01-21T12:09:46.334577Z",
     "shell.execute_reply": "2025-01-21T12:09:46.334062Z",
     "shell.execute_reply.started": "2025-01-21T12:09:46.277150Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# train_df = pd.DataFrame(train_labels_info)\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000]) # 取前45000张图片作为训练集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:]) # 取后5000张图片作为验证集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:08:23.507372Z",
     "start_time": "2025-01-20T08:08:22.474521Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:09:52.459096Z",
     "iopub.status.busy": "2025-01-21T12:09:52.458773Z",
     "iopub.status.idle": "2025-01-21T12:09:53.260685Z",
     "shell.execute_reply": "2025-01-21T12:09:53.260133Z",
     "shell.execute_reply.started": "2025-01-21T12:09:52.459071Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)} # 类别映射为idx\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)} # idx映射为类别,为了test测试集使用\n",
    "    def __init__(self, mode, transform=None):\n",
    "        self.df = self.df_map.get(mode, None) # 获取对应模式的df，不同字符串对应不同模式\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "        # assert self.df, \"df is None\"\n",
    "        self.transform = transform\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        img_path, label = self.df.iloc[index] # 获取图片路径和标签\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "        # # img 转换为 channel first\n",
    "        # img = img.transpose((2, 0, 1))\n",
    "        # transform\n",
    "        img = self.transform(img) # 数据增强\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "    \n",
    "    def __len__(self):\n",
    "        return self.df.shape[0] # 返回df的行数,样本数\n",
    "    \n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "transforms_train = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)), #缩放\n",
    "        # random rotation 40\n",
    "        transforms.RandomRotation(40), #随机旋转\n",
    "        # horizaontal flip\n",
    "        transforms.RandomHorizontalFlip(),  #随机水平翻转\n",
    "        transforms.ToTensor(), #转换为tensor\n",
    "        # transforms.Normalize(mean, std) #标准化\n",
    "    ]) #数据增强\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        transforms.ToTensor(),\n",
    "        # transforms.Normalize(mean, std)\n",
    "    ])\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:09:59.918609Z",
     "iopub.status.busy": "2025-01-21T12:09:59.917898Z",
     "iopub.status.idle": "2025-01-21T12:09:59.927039Z",
     "shell.execute_reply": "2025-01-21T12:09:59.926580Z",
     "shell.execute_reply.started": "2025-01-21T12:09:59.918571Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:10:06.519024Z",
     "iopub.status.busy": "2025-01-21T12:10:06.518682Z",
     "iopub.status.idle": "2025-01-21T12:10:06.524133Z",
     "shell.execute_reply": "2025-01-21T12:10:06.523689Z",
     "shell.execute_reply.started": "2025-01-21T12:10:06.519001Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "233.8"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sample1=train_ds[0][0]\n",
    "np.sum(sample1[2].numpy())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T12:26:52.532323800Z",
     "start_time": "2024-07-24T12:26:52.525328700Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T12:10:10.067435Z",
     "iopub.status.busy": "2025-01-21T12:10:10.067096Z",
     "iopub.status.idle": "2025-01-21T12:10:10.071886Z",
     "shell.execute_reply": "2025-01-21T12:10:10.071361Z",
     "shell.execute_reply.started": "2025-01-21T12:10:10.067414Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{0: 'airplane', 1: 'automobile', 2: 'bird', 3: 'cat', 4: 'deer', 5: 'dog', 6: 'frog', 7: 'horse', 8: 'ship', 9: 'truck'}\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'airplane': 0,\n",
       " 'automobile': 1,\n",
       " 'bird': 2,\n",
       " 'cat': 3,\n",
       " 'deer': 4,\n",
       " 'dog': 5,\n",
       " 'frog': 6,\n",
       " 'horse': 7,\n",
       " 'ship': 8,\n",
       " 'truck': 9}"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(train_ds.idx_to_label)  # 类别映射为idx\n",
    "train_ds.label_to_idx # idx映射为类别"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:08:29.255263Z",
     "start_time": "2025-01-20T08:08:29.252817Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:10:14.463827Z",
     "iopub.status.busy": "2025-01-21T12:10:14.463472Z",
     "iopub.status.idle": "2025-01-21T12:10:14.467129Z",
     "shell.execute_reply": "2025-01-21T12:10:14.466511Z",
     "shell.execute_reply.started": "2025-01-21T12:10:14.463805Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)   \n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T06:24:23.873550Z",
     "iopub.status.busy": "2025-01-21T06:24:23.873382Z",
     "iopub.status.idle": "2025-01-21T06:24:24.009644Z",
     "shell.execute_reply": "2025-01-21T06:24:24.009013Z",
     "shell.execute_reply.started": "2025-01-21T06:24:23.873531Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-rw-r--r-- 1 root root 0  1月 21 14:07 /mnt/workspace/competitions/cifar-10/train/17742.png\n"
     ]
    }
   ],
   "source": [
    "!ls -l /mnt/workspace/competitions/cifar-10/train/17742.png"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:10:20.915249Z",
     "iopub.status.busy": "2025-01-21T12:10:20.914893Z",
     "iopub.status.idle": "2025-01-21T12:10:40.773101Z",
     "shell.execute_reply": "2025-01-21T12:10:40.772594Z",
     "shell.execute_reply.started": "2025-01-21T12:10:20.915224Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.4366, 0.4265, 0.3944]), tensor([0.2467, 0.2421, 0.2361]))\n"
     ]
    }
   ],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:49:56.579457400Z",
     "start_time": "2024-07-23T06:49:56.499468400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:10:49.403434Z",
     "iopub.status.busy": "2025-01-21T12:10:49.402839Z",
     "iopub.status.idle": "2025-01-21T12:10:49.474123Z",
     "shell.execute_reply": "2025-01-21T12:10:49.473615Z",
     "shell.execute_reply.started": "2025-01-21T12:10:49.403410Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "             model.0.weight             paramerters num: 3456\n",
      "              model.0.bias              paramerters num: 128\n",
      "             model.2.weight             paramerters num: 128\n",
      "              model.2.bias              paramerters num: 128\n",
      "             model.3.weight             paramerters num: 147456\n",
      "              model.3.bias              paramerters num: 128\n",
      "             model.5.weight             paramerters num: 128\n",
      "              model.5.bias              paramerters num: 128\n",
      "             model.7.weight             paramerters num: 294912\n",
      "              model.7.bias              paramerters num: 256\n",
      "             model.9.weight             paramerters num: 256\n",
      "              model.9.bias              paramerters num: 256\n",
      "            model.10.weight             paramerters num: 589824\n",
      "             model.10.bias              paramerters num: 256\n",
      "            model.12.weight             paramerters num: 256\n",
      "             model.12.bias              paramerters num: 256\n",
      "            model.14.weight             paramerters num: 1179648\n",
      "             model.14.bias              paramerters num: 512\n",
      "            model.16.weight             paramerters num: 512\n",
      "             model.16.bias              paramerters num: 512\n",
      "            model.17.weight             paramerters num: 2359296\n",
      "             model.17.bias              paramerters num: 512\n",
      "            model.19.weight             paramerters num: 512\n",
      "             model.19.bias              paramerters num: 512\n",
      "            model.22.weight             paramerters num: 4194304\n",
      "             model.22.bias              paramerters num: 512\n",
      "            model.24.weight             paramerters num: 5120\n",
      "             model.24.bias              paramerters num: 10\n"
     ]
    }
   ],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes):\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=128, kernel_size=3, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(128),# 批标准化，在通道数做的归一化\n",
    "            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\"), #输出尺寸（128，32，32）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(128),\n",
    "            nn.MaxPool2d(kernel_size=2), #输出尺寸（128，16，16）\n",
    "            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=\"same\"), #输出尺寸（256，16，16）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.MaxPool2d(kernel_size=2),#输出尺寸（256，8，8）\n",
    "            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(512),\n",
    "            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=\"same\"), #输出尺寸（512，8，8）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(512),\n",
    "            nn.MaxPool2d(kernel_size=2), #输出尺寸（512，4，4）\n",
    "            nn.Flatten(), #展平\n",
    "            nn.Linear(8192, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(512, num_classes),\n",
    "        ) #Sequential自动连接各层，把各层的输出作为下一层的输入\n",
    "        \n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "        \n",
    "for key, value in CNN(len(class_names)).named_parameters():\n",
    "    print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:49:59.043655400Z",
     "start_time": "2024-07-23T06:49:58.976307900Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T12:10:53.356350Z",
     "iopub.status.busy": "2025-01-21T12:10:53.356007Z",
     "iopub.status.idle": "2025-01-21T12:10:53.417284Z",
     "shell.execute_reply": "2025-01-21T12:10:53.416783Z",
     "shell.execute_reply.started": "2025-01-21T12:10:53.356327Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 8779914\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in CNN(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:18:30.878296Z",
     "start_time": "2025-01-20T08:18:30.872291Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:02.112616Z",
     "iopub.status.busy": "2025-01-21T12:11:02.112258Z",
     "iopub.status.idle": "2025-01-21T12:11:02.122284Z",
     "shell.execute_reply": "2025-01-21T12:11:02.121758Z",
     "shell.execute_reply.started": "2025-01-21T12:11:02.112576Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([32, 3, 64, 64])"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "input_4d = torch.randn(32, 3, 64, 64)  # 32 个样本，3 个通道，图像大小为 64x64\n",
    "bn2d = nn.BatchNorm2d(3)              # 对 3 个通道进行归一化\n",
    "output_4d = bn2d(input_4d)\n",
    "output_4d.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:18:32.896417Z",
     "start_time": "2025-01-20T08:18:32.888443Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:05.595057Z",
     "iopub.status.busy": "2025-01-21T12:11:05.594738Z",
     "iopub.status.idle": "2025-01-21T12:11:05.607431Z",
     "shell.execute_reply": "2025-01-21T12:11:05.606856Z",
     "shell.execute_reply.started": "2025-01-21T12:11:05.595033Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[[-3.8881e-01, -5.5315e-01, -2.8980e-01,  ...,  1.5691e+00,\n",
       "           -1.4369e+00, -1.5667e-01],\n",
       "          [ 1.9169e+00,  9.2424e-01,  7.6277e-01,  ..., -3.6281e-01,\n",
       "            2.7444e-01,  1.4880e+00],\n",
       "          [ 1.8343e+00, -4.0674e-01, -1.5995e+00,  ..., -8.1249e-01,\n",
       "            2.8960e-01, -8.1628e-01],\n",
       "          ...,\n",
       "          [-9.2664e-01, -2.6060e-01, -1.3514e+00,  ..., -4.1452e-01,\n",
       "           -3.7280e-01,  1.4864e+00],\n",
       "          [ 6.6819e-01,  1.6019e+00,  6.4076e-01,  ...,  5.5162e-01,\n",
       "           -1.6149e+00, -5.2513e-01],\n",
       "          [ 3.0157e-02,  1.8756e+00,  7.3046e-01,  ..., -9.8423e-01,\n",
       "           -2.1898e-01, -2.0324e-01]],\n",
       "\n",
       "         [[ 1.7572e+00,  2.8287e-01, -6.0190e-01,  ..., -1.3252e+00,\n",
       "           -6.7602e-01, -3.5713e-01],\n",
       "          [ 8.3510e-01, -2.6174e-01,  2.7444e-01,  ..., -6.9942e-01,\n",
       "            3.8616e-01, -3.4620e-01],\n",
       "          [ 5.8071e-01,  1.2379e+00,  1.0582e+00,  ...,  5.2679e-01,\n",
       "            8.6059e-01,  1.6932e-01],\n",
       "          ...,\n",
       "          [ 8.4254e-01, -1.2295e+00, -9.3096e-01,  ...,  3.4878e-01,\n",
       "            4.7985e-01,  5.9425e-01],\n",
       "          [-3.8072e-02,  1.1905e+00, -3.1383e-01,  ..., -1.2931e+00,\n",
       "           -1.2314e+00,  7.3127e-01],\n",
       "          [ 1.7072e+00, -1.3547e+00,  5.7319e-01,  ...,  1.2737e+00,\n",
       "            6.0969e-01,  3.9790e-01]],\n",
       "\n",
       "         [[ 1.2921e-01,  1.6553e+00, -6.7293e-01,  ...,  5.0647e-01,\n",
       "           -1.6000e-01,  1.7553e+00],\n",
       "          [ 5.1758e-01,  1.8004e+00,  6.2817e-01,  ..., -2.5049e-02,\n",
       "           -1.4353e+00,  5.3713e-01],\n",
       "          [ 1.3433e-01, -5.4330e-01,  1.1007e-01,  ...,  7.9326e-01,\n",
       "           -4.7948e-01,  2.7845e-01],\n",
       "          ...,\n",
       "          [ 4.4724e-01, -1.6068e+00,  4.8181e-01,  ...,  6.9870e-01,\n",
       "           -2.8833e-01, -3.4288e-01],\n",
       "          [-3.9188e-01,  8.9040e-01, -3.5540e-01,  ...,  1.1827e+00,\n",
       "           -1.8134e+00,  1.0340e-01],\n",
       "          [-6.7386e-01,  7.0763e-01,  5.3598e-01,  ...,  1.5913e-02,\n",
       "            3.8703e-01, -1.2272e+00]]],\n",
       "\n",
       "\n",
       "        [[[ 1.3152e+00, -2.0218e+00,  1.7366e-01,  ..., -2.4707e+00,\n",
       "           -9.3356e-01, -1.6494e+00],\n",
       "          [ 1.1648e+00,  6.3571e-02,  5.4068e-01,  ...,  6.4701e-01,\n",
       "           -2.1745e-01, -9.0211e-01],\n",
       "          [-1.2850e+00,  2.6291e-01, -1.0205e+00,  ...,  1.0527e+00,\n",
       "           -3.1088e-01,  1.2225e+00],\n",
       "          ...,\n",
       "          [ 9.7689e-01, -9.7639e-01,  8.5392e-01,  ...,  2.1191e+00,\n",
       "            1.7390e+00,  1.1654e+00],\n",
       "          [-8.5262e-01,  8.3729e-01, -6.0678e-01,  ..., -1.0578e+00,\n",
       "           -4.2766e-01,  3.5250e-01],\n",
       "          [ 2.0170e+00,  4.6714e-01,  5.0154e-01,  ...,  9.5466e-01,\n",
       "            1.1307e+00, -1.5180e+00]],\n",
       "\n",
       "         [[-8.9024e-01, -2.3241e-01,  5.9218e-01,  ..., -1.6002e+00,\n",
       "            5.3383e-01,  6.9987e-01],\n",
       "          [ 5.7869e-01,  9.9072e-01,  1.2344e+00,  ..., -1.9590e-02,\n",
       "           -2.2039e-01, -1.3809e-02],\n",
       "          [-5.7585e-01,  6.9152e-01,  1.4681e+00,  ...,  4.6872e-01,\n",
       "            5.8613e-01, -1.1116e-01],\n",
       "          ...,\n",
       "          [ 6.8284e-01,  6.3757e-01,  8.9115e-02,  ..., -6.7005e-01,\n",
       "            4.5632e-01, -7.4933e-01],\n",
       "          [-3.9855e-01,  2.4635e-01, -1.8418e-02,  ...,  9.0780e-01,\n",
       "            8.5417e-01,  1.0653e+00],\n",
       "          [ 4.5272e-01, -2.6638e-01, -1.2495e+00,  ..., -9.9005e-01,\n",
       "            1.0689e+00, -1.4096e-01]],\n",
       "\n",
       "         [[ 1.7650e+00, -1.0115e-01,  4.5716e-02,  ...,  3.2461e-01,\n",
       "            2.9434e-01,  9.6861e-02],\n",
       "          [-8.4572e-02, -8.2457e-01, -1.4357e+00,  ...,  8.8279e-01,\n",
       "           -3.0678e-01,  4.8531e-01],\n",
       "          [-6.7244e-01,  4.4795e-01,  2.6352e-01,  ..., -6.7997e-01,\n",
       "           -8.2021e-01, -5.6261e-02],\n",
       "          ...,\n",
       "          [-5.2397e-01,  8.3622e-01,  5.5741e-01,  ..., -9.3251e-02,\n",
       "            1.1335e+00,  7.0344e-01],\n",
       "          [ 1.3232e-01, -2.7503e-01,  3.5391e-01,  ..., -1.7651e-01,\n",
       "           -9.1256e-01, -9.8937e-01],\n",
       "          [ 1.0470e+00,  1.4501e+00, -5.3789e-01,  ..., -1.4142e+00,\n",
       "           -7.3373e-01,  1.0674e-01]]],\n",
       "\n",
       "\n",
       "        [[[-8.7955e-01, -1.2760e+00,  7.9326e-01,  ..., -3.9520e-01,\n",
       "           -4.5627e-01, -9.5889e-02],\n",
       "          [ 7.1306e-01,  3.3967e-01,  4.1372e-01,  ...,  1.0902e-01,\n",
       "            1.0528e+00,  6.8632e-01],\n",
       "          [-9.7170e-01, -1.4614e+00,  3.3097e-02,  ...,  5.6436e-01,\n",
       "           -1.4917e-01,  3.6570e-02],\n",
       "          ...,\n",
       "          [-1.0221e+00,  3.1836e-01, -2.3416e+00,  ..., -2.1454e+00,\n",
       "           -1.8409e+00,  1.6266e+00],\n",
       "          [ 7.8723e-01,  5.7146e-01,  4.2886e-01,  ...,  4.3176e-01,\n",
       "            1.5016e+00, -4.1252e-01],\n",
       "          [-7.5044e-01,  1.1682e+00, -2.3504e-01,  ..., -2.5098e-01,\n",
       "           -1.2514e+00, -9.3069e-02]],\n",
       "\n",
       "         [[-7.4084e-01, -2.1429e-01, -1.1106e+00,  ..., -1.1475e+00,\n",
       "           -1.2047e+00,  6.8984e-01],\n",
       "          [ 3.1233e-01, -9.5212e-01,  7.8193e-01,  ...,  9.3888e-01,\n",
       "            1.4140e+00, -7.3746e-01],\n",
       "          [ 4.8839e-01, -4.3030e-01, -4.4550e-01,  ..., -1.1239e-01,\n",
       "           -2.3768e-01,  5.2681e-01],\n",
       "          ...,\n",
       "          [-2.6468e-01,  1.1898e-01, -7.6029e-01,  ...,  2.7769e+00,\n",
       "            2.2077e-01,  2.4758e+00],\n",
       "          [ 2.9064e-02,  1.3422e+00,  2.4349e+00,  ..., -1.3230e+00,\n",
       "           -2.8127e-01, -1.7503e+00],\n",
       "          [ 1.4479e+00, -2.6714e+00,  1.3100e+00,  ..., -1.2896e-01,\n",
       "            1.8236e-01,  9.9671e-01]],\n",
       "\n",
       "         [[ 3.7989e-01, -3.1311e-01,  1.3188e+00,  ..., -1.4641e+00,\n",
       "           -3.1875e+00, -7.3187e-01],\n",
       "          [ 3.7589e-02, -2.8258e-01,  6.8702e-01,  ..., -9.2006e-01,\n",
       "           -1.8741e-02,  3.1462e-01],\n",
       "          [-2.6540e-01,  2.5987e+00, -7.4705e-01,  ..., -3.2921e-01,\n",
       "           -2.0712e-02,  7.6582e-01],\n",
       "          ...,\n",
       "          [-1.4310e-01,  3.5546e-01, -2.5701e-01,  ...,  1.7042e+00,\n",
       "           -1.9087e-01,  2.7227e+00],\n",
       "          [-1.3834e+00, -5.7630e-01, -1.0583e+00,  ..., -1.0003e+00,\n",
       "           -9.5326e-01, -9.8735e-01],\n",
       "          [ 4.0751e-01, -8.5272e-01, -1.5243e+00,  ..., -8.2731e-01,\n",
       "            2.9050e-01,  3.2216e-01]]],\n",
       "\n",
       "\n",
       "        ...,\n",
       "\n",
       "\n",
       "        [[[-3.5949e+00,  4.7069e-01,  7.4905e-01,  ...,  1.4326e+00,\n",
       "           -1.1802e+00,  3.3691e-01],\n",
       "          [ 8.2307e-01,  5.3716e-01,  2.5195e-01,  ..., -5.5390e-01,\n",
       "           -9.1414e-01, -7.4210e-01],\n",
       "          [-1.9925e+00, -2.2825e-02, -2.1967e+00,  ..., -1.3481e+00,\n",
       "           -5.4434e-01,  5.1522e-01],\n",
       "          ...,\n",
       "          [-3.1445e-01,  4.6222e-02,  2.4078e-02,  ...,  1.5718e+00,\n",
       "           -9.8668e-01, -4.3107e-01],\n",
       "          [ 3.7877e-01,  4.8495e-01,  1.9430e-01,  ...,  4.3334e-01,\n",
       "           -2.6405e-01, -1.3838e-01],\n",
       "          [ 2.2378e-01,  4.6573e-01, -8.0401e-01,  ...,  3.0405e-01,\n",
       "           -3.5465e-01, -2.7851e-01]],\n",
       "\n",
       "         [[ 3.8906e-01, -5.1593e-01,  1.2716e+00,  ..., -1.2320e+00,\n",
       "            4.0141e-01, -1.3247e+00],\n",
       "          [-1.1730e+00,  1.1098e+00,  1.8218e+00,  ...,  2.7536e-01,\n",
       "           -6.9409e-01,  1.0894e+00],\n",
       "          [-9.4402e-01,  2.5011e+00,  9.1553e-01,  ..., -1.4288e+00,\n",
       "            1.4923e+00,  4.6363e-01],\n",
       "          ...,\n",
       "          [-4.6636e-01,  5.6354e-01,  1.3923e+00,  ...,  1.2392e+00,\n",
       "           -7.3580e-02, -1.7342e-01],\n",
       "          [-5.4760e-01, -1.7155e+00, -1.6624e+00,  ..., -2.6584e+00,\n",
       "           -1.5658e+00, -9.9603e-01],\n",
       "          [-8.6768e-01, -2.0768e-01,  1.5254e+00,  ...,  1.2880e+00,\n",
       "            3.4107e-01, -5.4521e-02]],\n",
       "\n",
       "         [[-3.6736e-01, -1.3939e+00, -7.9586e-01,  ...,  1.5691e+00,\n",
       "           -2.0779e-01,  1.1821e+00],\n",
       "          [-2.6102e-01, -1.2706e+00,  7.8583e-01,  ..., -9.9612e-02,\n",
       "            8.8005e-02, -7.5965e-02],\n",
       "          [ 8.6756e-01, -1.1038e+00,  1.5553e+00,  ...,  6.8085e-02,\n",
       "            7.1971e-01,  1.1131e+00],\n",
       "          ...,\n",
       "          [ 1.0580e+00,  8.8998e-01,  1.7592e+00,  ...,  1.3291e+00,\n",
       "            3.3458e-02,  4.7090e-01],\n",
       "          [ 4.0771e-01, -7.8540e-01, -2.8075e+00,  ..., -1.4832e+00,\n",
       "            2.5879e+00,  7.5365e-01],\n",
       "          [ 5.4783e-01,  4.5283e-01,  6.0456e-01,  ...,  6.6493e-01,\n",
       "            1.1317e-01, -1.6211e+00]]],\n",
       "\n",
       "\n",
       "        [[[-1.4934e+00,  2.7729e-01,  3.3780e+00,  ..., -1.8787e+00,\n",
       "           -4.9604e-02, -4.1892e-01],\n",
       "          [-3.2103e-01, -6.3846e-01, -4.4863e-01,  ...,  1.2741e+00,\n",
       "            5.2576e-01,  4.7368e-01],\n",
       "          [-2.5202e+00, -1.7496e-02,  5.7410e-01,  ...,  4.5700e-01,\n",
       "           -7.3166e-01, -1.6710e+00],\n",
       "          ...,\n",
       "          [ 3.4523e-01, -1.5854e-01, -7.5054e-01,  ..., -7.1226e-01,\n",
       "            1.9715e+00, -2.8087e-01],\n",
       "          [ 1.5059e-01, -6.1627e-01,  4.8934e-01,  ...,  3.1018e-02,\n",
       "            7.5604e-01,  9.3791e-01],\n",
       "          [-6.3946e-01, -4.2016e-01,  5.1195e-01,  ..., -6.9748e-01,\n",
       "            3.2263e-01, -4.8337e-01]],\n",
       "\n",
       "         [[ 1.4589e+00,  2.2018e+00, -4.9063e-01,  ...,  1.9072e+00,\n",
       "            1.1495e+00,  1.5022e+00],\n",
       "          [-5.5159e-01, -8.9413e-01, -5.5539e-01,  ..., -1.3681e-01,\n",
       "            8.7522e-01,  7.9084e-02],\n",
       "          [-5.7173e-01, -1.4850e+00, -6.4997e-01,  ...,  2.5052e-01,\n",
       "           -6.6007e-01,  2.0522e+00],\n",
       "          ...,\n",
       "          [-1.2692e+00,  3.8195e-01, -1.0012e+00,  ...,  1.2314e+00,\n",
       "           -7.5737e-01,  5.7108e-03],\n",
       "          [ 2.7264e-03, -5.4034e-01, -9.3500e-02,  ...,  2.2918e+00,\n",
       "            2.7048e-01,  1.5861e+00],\n",
       "          [ 7.7440e-02,  8.6845e-01,  5.3897e-01,  ..., -2.2738e-01,\n",
       "           -3.7676e-01,  1.1066e+00]],\n",
       "\n",
       "         [[-1.0898e+00, -6.7327e-01,  6.3444e-01,  ...,  3.9376e+00,\n",
       "           -1.4795e-01, -1.7404e+00],\n",
       "          [ 1.0616e+00, -6.7862e-01,  7.2579e-01,  ...,  8.9291e-01,\n",
       "            1.4191e+00, -1.3524e+00],\n",
       "          [ 6.2328e-02, -1.6727e+00, -4.0327e-01,  ...,  8.2137e-01,\n",
       "            1.0464e+00, -1.8530e-02],\n",
       "          ...,\n",
       "          [ 1.3627e+00, -1.1064e+00,  7.8309e-01,  ..., -3.2842e-01,\n",
       "           -1.2290e+00, -1.1708e+00],\n",
       "          [ 4.9976e-01,  1.0861e+00, -9.6739e-02,  ..., -1.1883e+00,\n",
       "            5.3398e-01, -6.3938e-01],\n",
       "          [-4.3690e-01,  8.4486e-01,  8.5831e-01,  ...,  5.5545e-02,\n",
       "           -6.2722e-01,  1.2740e-01]]],\n",
       "\n",
       "\n",
       "        [[[-9.4520e-01,  1.2192e+00,  1.7938e+00,  ...,  1.7046e+00,\n",
       "            8.7826e-01, -3.7043e-02],\n",
       "          [ 1.4872e+00,  6.9604e-01,  2.5608e-02,  ...,  6.2675e-01,\n",
       "            9.7716e-01, -9.0106e-01],\n",
       "          [ 1.5848e+00,  2.5557e-01, -1.6862e-01,  ...,  2.3533e+00,\n",
       "            1.4195e+00,  8.4134e-01],\n",
       "          ...,\n",
       "          [-4.0017e-01,  2.4626e-01,  1.1723e+00,  ...,  1.2316e+00,\n",
       "           -2.9661e-01,  1.1176e+00],\n",
       "          [-6.8495e-01,  8.0318e-01, -3.8501e-01,  ...,  2.8571e-01,\n",
       "           -3.9647e-01,  9.8780e-01],\n",
       "          [ 1.5077e+00, -2.6603e-02, -8.9147e-01,  ...,  6.5468e-01,\n",
       "            1.1512e+00, -9.7177e-01]],\n",
       "\n",
       "         [[-3.3848e-01, -4.4197e-01,  7.5037e-01,  ..., -2.3656e-01,\n",
       "           -1.5693e+00,  1.3902e+00],\n",
       "          [-7.1494e-02, -1.4280e-01, -4.9749e-01,  ...,  1.5141e-01,\n",
       "           -1.6738e-01,  1.7331e+00],\n",
       "          [-2.1526e+00,  4.8729e-01, -9.0166e-03,  ...,  3.7848e-01,\n",
       "           -4.4766e-01, -1.1178e+00],\n",
       "          ...,\n",
       "          [ 3.8549e-01,  6.2097e-01, -3.9334e-02,  ..., -3.9949e-01,\n",
       "            7.4338e-01, -9.6995e-01],\n",
       "          [-6.7090e-01,  1.5523e+00,  1.1217e+00,  ..., -1.0104e+00,\n",
       "           -2.2071e-01,  6.9165e-01],\n",
       "          [-1.2761e+00, -6.0267e-02,  8.0326e-01,  ...,  8.1957e-01,\n",
       "           -1.3474e+00, -7.3931e-01]],\n",
       "\n",
       "         [[-8.4081e-01,  1.0079e+00, -9.9006e-01,  ...,  1.1437e-02,\n",
       "           -4.4019e-01, -7.0799e-01],\n",
       "          [-2.4347e-01,  5.3422e-01, -9.2268e-01,  ...,  8.9745e-01,\n",
       "           -8.4978e-01,  3.7651e-01],\n",
       "          [-1.6394e+00,  4.7077e-01,  3.5734e-01,  ..., -6.8215e-01,\n",
       "            8.9782e-01, -2.8712e-01],\n",
       "          ...,\n",
       "          [ 2.4346e-01, -9.6266e-01, -4.7208e-01,  ..., -7.8701e-02,\n",
       "           -2.7209e-01, -1.1372e+00],\n",
       "          [ 8.1256e-01,  2.5199e+00, -1.9221e-01,  ...,  5.7638e-01,\n",
       "           -3.3003e-01,  5.8578e-02],\n",
       "          [-8.8917e-01, -1.7211e-01,  4.5087e-01,  ...,  1.0985e+00,\n",
       "           -6.7057e-01,  4.0637e-01]]]], grad_fn=<NativeBatchNormBackward0>)"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output_4d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:18:50.605471Z",
     "start_time": "2025-01-20T08:18:50.598876Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T06:31:47.751550Z",
     "iopub.status.busy": "2025-01-21T06:31:47.751193Z",
     "iopub.status.idle": "2025-01-21T06:31:47.759373Z",
     "shell.execute_reply": "2025-01-21T06:31:47.758935Z",
     "shell.execute_reply.started": "2025-01-21T06:31:47.751531Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[[-0.2953,  0.0927, -0.8414,  ...,  2.7265, -0.3434, -0.1716],\n",
       "          [ 0.4380, -0.3756, -0.6113,  ...,  0.3967,  1.8735, -1.4246],\n",
       "          [-0.3903, -1.0851, -0.4107,  ...,  1.1857, -0.2241, -1.6071],\n",
       "          ...,\n",
       "          [-1.2694, -1.7684, -0.8049,  ..., -0.7986,  0.4298,  0.7932],\n",
       "          [ 1.4989,  1.2909,  0.0298,  ..., -1.3217, -1.4292, -0.7924],\n",
       "          [ 0.5484, -0.8041, -0.4881,  ..., -0.3130,  0.2434,  0.8963]]],\n",
       "\n",
       "\n",
       "        [[[-0.5553,  0.8312,  1.6282,  ..., -0.4060, -0.2338,  2.7143],\n",
       "          [ 1.8864, -0.1496, -0.6375,  ..., -0.1513,  0.8496,  0.5877],\n",
       "          [ 3.7287,  0.1504,  0.5744,  ...,  0.3906, -0.5160, -0.1425],\n",
       "          ...,\n",
       "          [ 1.4511,  1.1423, -0.9488,  ...,  0.5015,  2.5165,  0.7380],\n",
       "          [-1.2180,  0.3759,  0.3726,  ...,  0.2073, -0.2367, -0.3928],\n",
       "          [ 0.5792, -0.2179, -1.1600,  ..., -0.1631,  1.2382, -0.6545]]],\n",
       "\n",
       "\n",
       "        [[[ 2.7718,  1.3064, -1.0857,  ..., -0.5394,  0.9598,  0.9424],\n",
       "          [ 1.1149,  0.3007, -1.1226,  ..., -0.3769,  0.9022, -0.6725],\n",
       "          [-1.2982,  0.9607,  1.5933,  ..., -3.9616,  1.3313, -0.4034],\n",
       "          ...,\n",
       "          [ 1.1023,  0.0377, -1.5308,  ..., -0.0129,  0.5228, -1.0084],\n",
       "          [ 0.8274,  0.7053,  0.2640,  ...,  0.3995,  0.1492, -0.2941],\n",
       "          [ 0.3114,  1.0250, -0.0728,  ..., -0.5126,  1.0384,  0.6825]]],\n",
       "\n",
       "\n",
       "        ...,\n",
       "\n",
       "\n",
       "        [[[-1.0012, -1.1448,  1.2792,  ..., -1.2840, -0.2207,  0.2309],\n",
       "          [-0.5533, -0.0589, -0.4661,  ..., -1.2492,  0.1161, -0.1074],\n",
       "          [-1.0006,  0.9144,  0.1525,  ..., -0.3971,  1.5844,  0.8213],\n",
       "          ...,\n",
       "          [-1.0144,  0.7773, -0.5937,  ...,  0.6349,  0.4685,  0.7281],\n",
       "          [-0.1227, -0.3948, -0.8798,  ..., -2.3503,  0.7177,  0.8185],\n",
       "          [-1.5876,  0.2171,  1.1969,  ..., -0.0792, -1.5092, -0.8910]]],\n",
       "\n",
       "\n",
       "        [[[ 0.0430,  0.0204, -0.4908,  ..., -0.9084,  0.0642,  1.4652],\n",
       "          [-0.4477, -0.3047, -0.3550,  ..., -0.4049, -0.3557, -1.3803],\n",
       "          [-0.9044, -0.2207, -0.5400,  ...,  1.3302, -1.2963,  0.6784],\n",
       "          ...,\n",
       "          [ 0.4657, -0.9120, -0.3178,  ...,  0.4780,  1.4124, -0.4167],\n",
       "          [ 1.0075,  0.8842,  2.0812,  ..., -1.3168, -2.1020,  0.6342],\n",
       "          [ 1.3173,  0.5339, -1.4976,  ...,  0.6561,  0.3988,  1.8145]]],\n",
       "\n",
       "\n",
       "        [[[-0.4167,  0.6314, -1.3858,  ...,  3.1776,  1.7799,  0.0682],\n",
       "          [ 0.2543, -0.2489, -0.0359,  ...,  0.1104,  0.0170,  1.9438],\n",
       "          [-0.8901,  0.1647,  0.4425,  ...,  0.7012,  0.7987, -0.0121],\n",
       "          ...,\n",
       "          [ 0.4738,  0.1130, -0.4860,  ..., -0.1663,  0.4772,  1.5116],\n",
       "          [-1.0257,  0.0446,  0.3969,  ...,  0.4739,  1.4416,  0.8281],\n",
       "          [ 1.7814,  1.1830, -0.1155,  ...,  1.9083, -0.1385,  2.0294]]]],\n",
       "       grad_fn=<NativeBatchNormBackward0>)"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bn2d = nn.BatchNorm2d(1)              # 对 3 个通道进行归一化\n",
    "output_4d1 = bn2d(input_4d[:, 0:1, :, :])\n",
    "output_4d1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:47.038660500Z",
     "start_time": "2024-07-23T06:51:46.193237300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:12.792548Z",
     "iopub.status.busy": "2025-01-21T12:11:12.792223Z",
     "iopub.status.idle": "2025-01-21T12:11:12.819260Z",
     "shell.execute_reply": "2025-01-21T12:11:12.818740Z",
     "shell.execute_reply.started": "2025-01-21T12:11:12.792525Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:50.921239100Z",
     "start_time": "2024-07-23T06:51:49.522951600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:15.220169Z",
     "iopub.status.busy": "2025-01-21T12:11:15.219714Z",
     "iopub.status.idle": "2025-01-21T12:11:15.280877Z",
     "shell.execute_reply": "2025-01-21T12:11:15.280387Z",
     "shell.execute_reply.started": "2025-01-21T12:11:15.220144Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:52.029341200Z",
     "start_time": "2024-07-23T06:51:52.023343500Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:17.825611Z",
     "iopub.status.busy": "2025-01-21T12:11:17.825135Z",
     "iopub.status.idle": "2025-01-21T12:11:17.831175Z",
     "shell.execute_reply": "2025-01-21T12:11:17.830537Z",
     "shell.execute_reply.started": "2025-01-21T12:11:17.825571Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:56.837899700Z",
     "start_time": "2024-07-23T06:51:56.828651400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:20.403419Z",
     "iopub.status.busy": "2025-01-21T12:11:20.403084Z",
     "iopub.status.idle": "2025-01-21T12:11:20.408037Z",
     "shell.execute_reply": "2025-01-21T12:11:20.407408Z",
     "shell.execute_reply.started": "2025-01-21T12:11:20.403396Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:11:23.893118Z",
     "iopub.status.busy": "2025-01-21T12:11:23.892680Z",
     "iopub.status.idle": "2025-01-21T12:11:24.023924Z",
     "shell.execute_reply": "2025-01-21T12:11:24.023388Z",
     "shell.execute_reply.started": "2025-01-21T12:11:23.893094Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1) #最大值的索引\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())     # 计算准确率\n",
    "                loss = loss.cpu().item() # 计算损失\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step # 记录每一步的损失和准确率\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "model = CNN(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "# tensorboard_callback = TensorBoardCallback(\"runs/cifar-10\")\n",
    "# tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints/cifar-10\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T12:12:02.323095Z",
     "iopub.status.busy": "2025-01-21T12:12:02.322736Z",
     "iopub.status.idle": "2025-01-21T12:21:39.598278Z",
     "shell.execute_reply": "2025-01-21T12:21:39.597776Z",
     "shell.execute_reply.started": "2025-01-21T12:12:02.323072Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 95%|█████████▌| 13376/14080 [09:37<00:30, 23.17it/s, epoch=18]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 19 / global_step 13376\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "record = training(\n",
    "    model,\n",
    "    train_dl,\n",
    "    eval_dl,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:21:58.627669Z",
     "iopub.status.busy": "2025-01-21T12:21:58.627309Z",
     "iopub.status.idle": "2025-01-21T12:21:58.862523Z",
     "shell.execute_reply": "2025-01-21T12:21:58.862050Z",
     "shell.execute_reply.started": "2025-01-21T12:21:58.627644Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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atUKgK5duwZ1fuFs8uTJTJo0yf48Ly+PFi1aMGLECNLS0oI+nslkYsGCBQwfPhxjHN7Vk/ijS+KPrliN/77V8wEo0IyMHu29C3Ksxh8ok8nErFmzonb+ap88CSEiL9mo589nwz9WRFVV8vPySU1L9dltLxx69+7t9LygoIBnnnmG2bNn25OR4uJiMjMzfR7n3HPPtT+uVasWqampHDt2LOh4duzYwaWXXuq0bODAgbz++utYLBaGDx9Oq1atOOuss7j44ou5+OKLueyyy0hJSaFbt25cdNFFdO3alZEjRzJixAj+/ve/U69evaDjqI4aN25MTk6O07KcnBzS0tI8tjoBJCYmkpiY6LbcaDRW6uKjsvtHm8QfXRJ/dMVy/IHEFcvxx7IaVW1POqwIERmKopCSYIjIv+QEvc/1gY538sd1LNBDDz3EDz/8wAsvvMDy5cvZvHkzXbt2payszOdxXL+IFEVBVcPfAp6amsrvv//Ol19+SZMmTXjqqafo1q0bubm56PV6FixYwK+//kqnTp148803ad++Pfv37w97HPGof//+LFy40GnZggUL6N+/f5QiEkKIMJIL3oiq9smT0++P9PcXosZLSEjAYvHfF3zlypVMmDCByy67jK5du9K4cWMOHDgQ+QDLdezYkZUrV7rF1K5dO/R6a2ubwWBg2LBhvPzyy/zxxx8cOHCARYsWAdakbeDAgUyZMoVNmzaRkJDADz/8UGXxV6WCggI2b97M5s2bAWsp8s2bN9tbCSdPnsxNN91k3/6uu+5i3759PPLII+zcuZN33nmHr7/+mgceeCAa4QshhIgjNarbnqROQojWrVuzdu1aDhw4QO3atb22CrVt25bvv/+esWPHoigKTz75ZERakLx58MEH6dOnD8899xxXX301q1ev5q233uKdd94B4JdffmHfvn0MHjyYevXqMWfOHFRVpX379qxdu5aFCxcyYsQI0tPTWbt2LcePH6djx45VFn9V2rBhA0OHDrU/t41NGj9+PDNmzCArK8upu2WbNm2YPXs2DzzwAG+88QbNmzfnww8/lDLlQgRBVTWemLWNjk3SuPG8VtEOR8SJNftO8tnqgzw1thMZaUnRDickNSt5kuxJiBrvoYceYvz48XTq1Ini4mI+/vhjj9tNnTqVW265hQEDBtCwYUMeffRRe3nqqtCzZ0++/vprnnrq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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T13:19:34.710194Z",
     "iopub.status.busy": "2025-01-21T13:19:34.709835Z",
     "iopub.status.idle": "2025-01-21T13:19:34.840547Z",
     "shell.execute_reply": "2025-01-21T13:19:34.839966Z",
     "shell.execute_reply.started": "2025-01-21T13:19:34.710169Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-rw-r--r-- 1 root root 35146294  1月 21 20:21 checkpoints/cifar-10/best.ckpt\n"
     ]
    }
   ],
   "source": [
    "!ls -l checkpoints/cifar-10/best.ckpt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T13:19:58.813911Z",
     "iopub.status.busy": "2025-01-21T13:19:58.813405Z",
     "iopub.status.idle": "2025-01-21T13:20:00.597393Z",
     "shell.execute_reply": "2025-01-21T13:20:00.596877Z",
     "shell.execute_reply.started": "2025-01-21T13:19:58.813886Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4496\n",
      "accuracy: 0.8672\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/cifar-10/best.ckpt\", weights_only=True, map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 推理 （打比赛）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T13:31:11.831837Z",
     "iopub.status.busy": "2025-01-21T13:31:11.831471Z",
     "iopub.status.idle": "2025-01-21T13:32:59.482148Z",
     "shell.execute_reply": "2025-01-21T13:32:59.481613Z",
     "shell.execute_reply.started": "2025-01-21T13:31:11.831813Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 4688/4688 [01:47<00:00, 43.55it/s]\n"
     ]
    }
   ],
   "source": [
    "# test_df\n",
    "test_ds = Cifar10Dataset(\"test\", transform=transforms_eval)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, drop_last=False)\n",
    "\n",
    "preds_collect = [] # 预测结果收集器\n",
    "model.eval()\n",
    "for data, fake_label in tqdm(test_dl):\n",
    "    data = data.to(device=device)\n",
    "    logits = model(data) #得到预测结果\n",
    "    preds = [test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()] # 得到预测类别，idx_to_label是id到字符串类别的映射\n",
    "    preds_collect.extend(preds)\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T13:33:03.156020Z",
     "iopub.status.busy": "2025-01-21T13:33:03.155665Z",
     "iopub.status.idle": "2025-01-21T13:33:03.164088Z",
     "shell.execute_reply": "2025-01-21T13:33:03.163515Z",
     "shell.execute_reply.started": "2025-01-21T13:33:03.155996Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>filepath</th>\n",
       "      <th>class</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>competitions/cifar-10/test/1.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>competitions/cifar-10/test/2.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>competitions/cifar-10/test/3.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>competitions/cifar-10/test/4.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>competitions/cifar-10/test/5.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           filepath class\n",
       "0  competitions/cifar-10/test/1.png   cat\n",
       "1  competitions/cifar-10/test/2.png   cat\n",
       "2  competitions/cifar-10/test/3.png   cat\n",
       "3  competitions/cifar-10/test/4.png   cat\n",
       "4  competitions/cifar-10/test/5.png   cat"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T13:33:20.401572Z",
     "iopub.status.busy": "2025-01-21T13:33:20.401228Z",
     "iopub.status.idle": "2025-01-21T13:33:20.462856Z",
     "shell.execute_reply": "2025-01-21T13:33:20.462331Z",
     "shell.execute_reply.started": "2025-01-21T13:33:20.401549Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299995</th>\n",
       "      <td>299996</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299996</th>\n",
       "      <td>299997</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299997</th>\n",
       "      <td>299998</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299998</th>\n",
       "      <td>299999</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999</th>\n",
       "      <td>300000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>300000 rows × 1 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "            id\n",
       "0            1\n",
       "1            2\n",
       "2            3\n",
       "3            4\n",
       "4            5\n",
       "...        ...\n",
       "299995  299996\n",
       "299996  299997\n",
       "299997  299998\n",
       "299998  299999\n",
       "299999  300000\n",
       "\n",
       "[300000 rows x 1 columns]"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df1 = pd.DataFrame(list(range(1,3*10**5+1)), columns=[\"id\"])\n",
    "test_df1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T13:33:27.387607Z",
     "iopub.status.busy": "2025-01-21T13:33:27.387248Z",
     "iopub.status.idle": "2025-01-21T13:33:27.401441Z",
     "shell.execute_reply": "2025-01-21T13:33:27.400864Z",
     "shell.execute_reply.started": "2025-01-21T13:33:27.387568Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "      <td>ship</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>bird</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   id       label\n",
       "0   1    airplane\n",
       "1   2    airplane\n",
       "2   3  automobile\n",
       "3   4        ship\n",
       "4   5        bird"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df1[\"label\"] = preds_collect # 增加预测类别列,比赛要求这一列是label\n",
    "test_df1.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T13:33:32.553677Z",
     "iopub.status.busy": "2025-01-21T13:33:32.553333Z",
     "iopub.status.idle": "2025-01-21T13:33:32.722445Z",
     "shell.execute_reply": "2025-01-21T13:33:32.721960Z",
     "shell.execute_reply.started": "2025-01-21T13:33:32.553654Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 导出 submission.csv\n",
    "test_df1.to_csv(\"submission.csv\", index=False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
